package com.yygo.webapp.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.yygo.model.*;
import com.yygo.service.DealerManager;
import org.apache.commons.lang.StringUtils;
import com.yygo.Constants;
import com.yygo.service.RoleManager;
import com.yygo.service.UserExistsException;
import com.yygo.service.UserManager;
import com.yygo.webapp.util.RequestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.mail.MailException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Implementation of <strong>SimpleFormController</strong> that interacts with
 * the {@link UserManager} to retrieve/persist values to the database.
 *
 * <p><a href="UserFormController.java.html"><i>View Source</i></a>
 *
 * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
 */
@Controller
@RequestMapping("/userform*")
public class UserFormController extends BaseFormController {

    private RoleManager roleManager;

    @Autowired
    public void setRoleManager(final RoleManager roleManager) {
        this.roleManager = roleManager;
    }

    @Autowired
    private DealerManager dealerManager;

    public UserFormController() {
        setCancelView("redirect:/home");
        setSuccessView("redirect:/admin/users");
    }

    @Override
    @InitBinder
    protected void initBinder(final HttpServletRequest request, final ServletRequestDataBinder binder) {
        super.initBinder(request, binder);
        //binder.setDisallowedFields("password", "confirmPassword");
    }

    /**
     * Load user object from db before web data binding in order to keep properties not populated from web post.
     * 
     * @param request
     * @return
     */
    @ModelAttribute("user")
    protected User loadUser(final HttpServletRequest request) {
        final String userId = request.getParameter("id");
        if (isFormSubmission(request) && StringUtils.isNotBlank(userId)) {
            return getUserManager().getUser(userId);
        }
        return new User();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String onSubmit(@ModelAttribute("user") final User user, final BindingResult errors, final HttpServletRequest request,
            final HttpServletResponse response)
            throws Exception {
        if (request.getParameter("cancel") != null) {
            if (StringUtils.equals(request.getParameter("from"), "dealeradmin")) {
                return "redirect:/dealerusers";
            } else if (StringUtils.equals(request.getParameter("from"), "bankadmin")) {
                return "redirect:/bankusers";
            } else if (!StringUtils.equals(request.getParameter("from"), "list")) {
                return getCancelView();
            } else {
                return getSuccessView();
            }
        }

        if (validator != null) { // validator is null during testing
            validator.validate(user, errors);

            if (errors.hasErrors() && request.getParameter("delete") == null) { // don't validate when deleting
                return "userform";
            }
        }

        log.debug("entering 'onSubmit' method...");

        final Locale locale = request.getLocale();

        if (request.getParameter("delete") != null) {
            getUserManager().removeUser(user.getId().toString());
            saveMessage(request, getText("user.deleted", user.getName(), locale));

            if (StringUtils.equals(request.getParameter("from"), "dealeradmin")) {
                return "redirect:/dealerusers";
            } else if (StringUtils.equals(request.getParameter("from"), "bankadmin")) {
                return "redirect:/bankusers";
            } else {
                return getSuccessView();
            }
        } else {
            // only attempt to change roles if user is admin for other users,
            // showForm() method will handle populating
            if (request.isUserInRole(Constants.ADMIN_ROLE) || request.isUserInRole(Constants.DEALER_ADMIN_ROLE) || request.isUserInRole(Constants.Bank_ADMIN_ROLE)) {
                final String[] userRoles = request.getParameterValues("userRoles");

                if (userRoles != null) {
                    user.getRoles().clear();
                    for (final String roleName : userRoles) {
                        user.addRole(roleManager.getRole(roleName));
                    }
                }
            } else {
                // if user is not an admin then load roles from the database
                // (or any other user properties that should not be editable
                // by users without admin role)
                final User cleanUser = getUserManager().getUserByUsername(
                        request.getRemoteUser());
                user.setRoles(cleanUser.getRoles());
            }
            final Integer originalVersion = user.getVersion();
            if(user.getId()==null){
                User nameUser = getUserManager().getUserByUsername(user.getUsername());
                User emailUser = getUserManager().getUserByEmail(user.getEmail());
                if (nameUser!=null){
                    errors.rejectValue("username", "errors.username.exist", new String[]{user.getUsername()}, "username is exist");
                }
                if (emailUser!=null){
                    errors.rejectValue("email", "errors.email.exist", new String[]{user.getEmail()}, "email is exist");
                }
                if (errors.hasErrors()) {
                    copyErrors(request, errors);
                    user.setVersion(originalVersion);
                    return "userform";
                }
            }
            // set a random password if user is added by admin
            /*if (originalVersion == null && StringUtils.isBlank(user.getPassword())) {
                user.setPassword(UUID.randomUUID().toString());
            }*/

            try {
                if (StringUtils.equals(request.getParameter("from"), "dealeradmin")) {
                    User dealerAdmin = getUserManager().getUserByUsername(request.getRemoteUser());
                    Dealer dealer = dealerAdmin.getDealer();
                    user.setDealer(dealer);
                    getUserManager().saveUser(user);
                } else if (StringUtils.equals(request.getParameter("from"), "bankadmin")) {
                    User bankAdmin = getUserManager().getUserByUsername(request.getRemoteUser());
                    Bank bank = bankAdmin.getBank();
                    user.setBank(bank);
                    getUserManager().saveUser(user);
                } else {
                    getUserManager().saveUser(user);
                }
            } catch (final AccessDeniedException ade) {
                // thrown by UserSecurityAdvice configured in aop:advisor userManagerSecurity
                log.warn(ade.getMessage());
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                return null;
            } catch (final UserExistsException e) {
                return "userform";
            }

            if (StringUtils.equals(request.getParameter("from"), "dealeradmin")) {
                saveMessage(request, getText("user.saved", user.getName(), locale));

                // return to main Menu
                return "redirect:/dealerusers";
            } else if (StringUtils.equals(request.getParameter("from"), "bankadmin")) {
                saveMessage(request, getText("user.saved", user.getName(), locale));

                // return to main Menu
                return "redirect:/bankusers";
            } else if (!StringUtils.equals(request.getParameter("from"), "list")) {
                saveMessage(request, getText("user.saved", user.getName(), locale));

                // return to main Menu
                return getCancelView();
            }  else {
                if (StringUtils.isBlank(request.getParameter("version"))) {
                    saveMessage(request, getText("user.added", user.getName(), locale));

                    // Send an account information e-mail
                    message.setSubject(getText("signup.email.subject", locale));

                    try {
                        final String resetPasswordUrl = getUserManager().buildRecoveryPasswordUrl(user,
                                UpdatePasswordController.RECOVERY_PASSWORD_TEMPLATE);
                        sendUserMessage(user, getText("newuser.email.message", user.getName(), locale),
                                RequestUtil.getAppURL(request) + resetPasswordUrl);
                    } catch (final MailException me) {
                        saveError(request, me.getCause().getLocalizedMessage());
                    }

                    return getSuccessView();
                } else {
                    saveMessage(request, getText("user.updated.byAdmin", user.getName(), locale));
                }
            }
        }
        return "userform";
    }

    @ModelAttribute
    @RequestMapping(method = RequestMethod.GET)
    protected User showForm(final HttpServletRequest request, final HttpServletResponse response)
            throws Exception {
        // If not an administrator, make sure user is not trying to add or edit another user
        if (!(request.isUserInRole(Constants.ADMIN_ROLE) || request.isUserInRole(Constants.DEALER_ADMIN_ROLE) || request.isUserInRole(Constants.Bank_ADMIN_ROLE)) && !isFormSubmission(request)) {
            if (isAdd(request) || request.getParameter("id") != null) {
                response.sendError(HttpServletResponse.SC_FORBIDDEN);
                log.warn("User '" + request.getRemoteUser() + "' is trying to edit user with id '" +
                        request.getParameter("id") + "'");

                throw new AccessDeniedException("You do not have permission to modify other users.");
            }
            // dealer admin can only edit dealer's user
            if (request.isUserInRole(Constants.DEALER_ADMIN_ROLE) && !isAdd(request)) {
                User dealerAdmin = getUserManager().getUserByUsername(request.getRemoteUser());
                String userId = request.getParameter("id");
                User editUser = getUserManager().getUser(userId);
                if (dealerAdmin.getDealer() == null || !dealerAdmin.getDealer().equals(editUser.getDealer())) {
                    throw new AccessDeniedException("You do not have permission to modify other users.");
                }
            } else if (request.isUserInRole(Constants.Bank_ADMIN_ROLE) && !isAdd(request)) {
                User bankAdmin = getUserManager().getUserByUsername(request.getRemoteUser());
                String userId = request.getParameter("id");
                User editUser = getUserManager().getUser(userId);
                if (bankAdmin.getBank() == null || !bankAdmin.getBank().equals(editUser.getBank())) {
                    throw new AccessDeniedException("You do not have permission to modify other users.");
                }
            }
        }
        if (!isFormSubmission(request)) {
            final String userId = request.getParameter("id");
            User user;
            if (userId == null && !isAdd(request)) {
                user = getUserManager().getUserByUsername(request.getRemoteUser());
            } else if (!StringUtils.isBlank(userId) && !"".equals(request.getParameter("version"))) {
                user = getUserManager().getUser(userId);
            } else {
                user = new User();
            }
            return user;
        }
        else {
            // populate user object from database, so all fields don't need to be hidden fields in form
            return getUserManager().getUser(request.getParameter("id"));
        }
    }

    private boolean isFormSubmission(final HttpServletRequest request) {
        return request.getMethod().equalsIgnoreCase("post");
    }

    protected boolean isAdd(final HttpServletRequest request) {
        final String method = request.getParameter("method");
        return (method != null && method.equalsIgnoreCase("add"));
    }

    @ModelAttribute("availableRoles")
    protected List<LabelValue> loadAvailableRoles() {
        String text = getText(Constants.ROLES, Locale.ENGLISH);
        List<LabelValue> list = new ArrayList<LabelValue>();
        String[] values = text.split("(\\s)*,(\\s)*");
        for (String value: values) {
            list.add(new LabelValue(getText(Constants.ROLES + "_" + value, LocaleContextHolder.getLocale()), Constants.ROLES + "_" + value));
        }

        return list;
    }

    private void copyErrors(HttpServletRequest request, BindingResult errors) {
        List<ObjectError> errorMessages = errors.getAllErrors();
        for (ObjectError err : errorMessages) {
            saveError(request, getText(err.getCode(), err.getArguments(), request.getLocale()));
        }
    }
}


